/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */
package com.obixlabs.commons.util;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;


/**
 * <p>
 * A utility class which simplifies the task of generating unique {@link #hashCode() hashCodes}  for
 * {@link Object objects}. This class generally uses an algorithm of the form 
 * <b><code>(A * B) + C</code></b> where: <b><code>A</code></b> and 
 * <b><code>C</code></b> are prime numbers, and <b><code>B</code></b>
 * is any number that identifies the object whose {@link #hashCode() hashCode} is to be calculated.
 * In some cases it may be simple to take <b><code>B</code></b> from the  {@link #hashCode() hashCode}
 * of any field that identifies the class or to use a time dependent random variable.
 * </p>
 */
public class SimpleHashCodeCalculator
{
	
	/**
	 * <p>The default value for  <b><code>A</code></b></p>.
	 */
	private static final int DEFAULT_A = 37;
	
    /**
     * <p>The default value for  <b><code>C</code></b></p>.
     */	
	private static final int DEFAULT_C = 7;
	
	/**
	 * <p>
	 * Calculates a unique code given the values of <b><code>A</code></b>, <b><code>B</code></b> and 
	 * <b><code>C</code></b>.  The code is calculated as <b><code>(A * B) + C</code></b>. This 
	 * method allows the caller full control over the values from which the code is calculated. As mentioned in the class
	 * description, the value <b><code>B</code></b> is any number that bears relevance to the object whose 
	 * {@link #hashCode() hashCode} is being calculated, whilst the remaining two arguments are prime numbers of 
	 * suitable value. 
	 *  </p>
	 *  
	 * @param a    The value for <b><code>A</code></b>.
	 * @param b    The value for <b><code>B</code></b>. The choice of value for this parameter should ideally guarantee that they 
	 * are unique for each instance of a given type. 
	 * @param c    The value for <b><code>C</code></b>.
	 * @return     A unique numeric code, in the context of <b><code>B</code></b>.
	 */
	public static final int calculate(int a, int b, int c)
	{return (a * b) + c;}
	
	/**
	 * <p>
	 * Similar to {@link #calculate(int, int, int)}, except that the default value {@value #DEFAULT_A} is used
	 * as the value of  <b><code>A</code></b>. 
	 * </p>
	 * 
     * @param b    The value for <b><code>B</code></b>. The choice of value for this parameter 
     * should ideally guarantee that they are unique for each instance of a given type. 
     * @param c    The value for <b><code>C</code></b>.
     * @return     A unique numeric code, in the context of <b><code>B</code></b>.
	 */
	public static final int calculate(int b, int c)
	{return calculate(DEFAULT_A,b, c);}

	/**
	 * <p>
	 * A variation of the method {@link #calculate(int, int, int)} which uses the default values {@value #DEFAULT_A}
	 * and {@value #DEFAULT_C} for <b><code>A</code></b> and <b><code>C</code></b> respectively.
	 * </p>
	 * 
     * @param b    The value for <b><code>B</code></b>. The choice of value for this parameter 
     * should ideally guarantee that they are unique for each instance of a given type. 
     * @return     A unique numeric code, in the context of <b><code>B</code></b>.
	 */
	public static final int calculate(int b)
	{return calculate(DEFAULT_A,b, DEFAULT_C);}

	/**
	 * <p>
	 * Can be used to calculate the {@link #hashCode() hashCode} of an object from 
	 * its constituent attributes. This method 
	 * is the recommended way to utilise this class. 
	 * It will iterate through each of the specified attributes, 
	 * successively refining an initial choice of hashCode.
	 * </p>
	 * <p>
	 * <b>Note</b> that if any of the specified attributes 
	 * is a {@link Collection collection}, this method
	 * will expand it out, thus treating its elements as if they
	 * were root attributes of the object instance.
	 * </p>
	 * 
	 * @param attrs The attributes from which to calculate a numeric code.
	 * @return     A code which is unique to the specified attributes.
	 */
	public static final int calculateFromFields(Object...attrs)
	{	        
		int result = 1;		
		for (int i =0;i<attrs.length;i++)
		{
			if (attrs[i]!=null)
			{
				if (attrs[i] instanceof Collection<?>)
				{
					result =  result + 
						sumElementHashcodes(((Collection<?>)attrs[i]).toArray());
				}
				else if (attrs[i] instanceof Map<?,?>)
				{
					result =  result + 
						calculateFromFields(
								((Map<?, ?>)attrs[i]).keySet(),
									((Map<?, ?>)attrs[i]).values());					
				}
				else if (attrs[i].getClass().isArray())
				{
					result = result + 
							sumElementHashcodes(attrs[i]);
				}				
				else result = result + attrs[i].hashCode();
			}
		}
		
		return calculate(result);
	}
	
	
	/**
	 * <p>
	 * Builds an identifier for the given {@link String string} from 
	 * its constituent characters.
	 * </p>
	 * 
	 * @param input The {@link String string} for which to build an identifier.
	 * @return     	A numeric identifier for the given {@link String string}.
	 */
	public static final int stringId(String input)
	{
		int result = 0;
		
		char[] inputChars = input.toCharArray();
		for (char xter : inputChars) 
			result += Character.getNumericValue(xter);
		
		return calculate(result);
	}	
	
	/**
	 * <p>
	 * Sums up the hashcodes of the elements in 
	 * the given array. 
	 * </p>
	 * 
	 * @param object	The array containing the elements,
	 * whose hashcodes should be summed.
	 * 
	 * @return	The sum of the hashcodes of the array 
	 * elements.
	 */
	private static int sumElementHashcodes(Object object)
	{
		int result=0;
		
		Object element;		
		for (int i=0;i<Array.getLength(object);i++)
		{
			element = Array.get(object, i); 
			if (element !=null) result += Array.get(object, i).hashCode();
		}
		return result;
	}
}